import React, { useEffect, useMemo } from "react";
import {
  ViewProps,
  Dimensions,
  Platform,
  StyleSheet,
  ViewStyle,
} from "react-native";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  withSpring,
  runOnJS,
} from "react-native-reanimated";

const styles = StyleSheet.create({
  base: {
    ...Platform.select({
      web: {
        flexDirection: "column",
        position: "relative",
        zIndex: 0,
        borderWidth: 0,
        minWidth: 0,
        minHeight: 0,
        backgroundColor: "transparent",
        alignItems: "stretch",
        margin: 0,
        padding: 0,
        cursor: "move",
        touchAction: "none",
        userSelect: "none",
      },
      default: {},
    }),
  },
  disabled: {
    opacity: 0.5,
    ...Platform.select({
      web: {
        cursor: "not-allowed",
      },
      default: {},
    }),
  },
});

type IDraggableProps = ViewProps & {
  style?: ViewStyle;
  onDragEnd?: (position: { x: number; y: number }) => void;
  initialPosition?: { x: number; y: number };
  disabled?: boolean;
  bounds?: {
    left?: number;
    right?: number;
    top?: number;
    bottom?: number;
  };
};

const springConfig = {
  damping: 15,
  stiffness: 150,
  mass: 1,
};

const Draggable = React.forwardRef<
  React.ComponentRef<typeof Animated.View>,
  IDraggableProps
>(function Draggable(
  {
    style,
    onDragEnd,
    initialPosition = { x: 0, y: 0 },
    disabled = false,
    bounds,
    ...props
  },
  ref
) {
  const translateX = useSharedValue(initialPosition.x);
  const translateY = useSharedValue(initialPosition.y);
  const contextX = useSharedValue(0);
  const contextY = useSharedValue(0);
  const elementSize = useSharedValue({ width: 0, height: 0 });

  const { width, height } = Dimensions.get("window");
  const maxX = useSharedValue(width);
  const maxY = useSharedValue(height);

  const checkBounds = useMemo(
    () => (x: number, y: number) => {
      "worklet";
      if (bounds) {
        return {
          x: Math.min(
            Math.max(x, bounds.left ?? 0),
            bounds.right ?? maxX.value
          ),
          y: Math.min(
            Math.max(y, bounds.top ?? 0),
            bounds.bottom ?? maxY.value
          ),
        };
      }

      return {
        x: Math.min(Math.max(x, 0), maxX.value - elementSize.value.width),
        y: Math.min(Math.max(y, 0), maxY.value - elementSize.value.height),
      };
    },
    [bounds, maxX.value, maxY.value, elementSize.value]
  );

  const gesture = useMemo(
    () =>
      Gesture.Pan()
        .enabled(!disabled)
        .onStart(() => {
          "worklet";
          contextX.value = translateX.value;
          contextY.value = translateY.value;
        })
        .onUpdate((event) => {
          "worklet";
          const newX = event.translationX + contextX.value;
          const newY = event.translationY + contextY.value;
          const { x, y } = checkBounds(newX, newY);
          translateX.value = x;
          translateY.value = y;
        })
        .onEnd(() => {
          "worklet";
          const { x, y } = checkBounds(translateX.value, translateY.value);
          translateX.value = withSpring(x, springConfig);
          translateY.value = withSpring(y, springConfig);
          if (onDragEnd) {
            runOnJS(onDragEnd)({ x, y });
          }
        }),
    [
      disabled,
      contextX,
      translateX,
      contextY,
      translateY,
      checkBounds,
      onDragEnd,
    ]
  );

  useEffect(() => {
    const subscription = Dimensions.addEventListener("change", ({ window }) => {
      maxX.value = bounds?.right ?? window.width;
      maxY.value = bounds?.bottom ?? window.height;
      const { x, y } = checkBounds(translateX.value, translateY.value);
      translateX.value = withSpring(x, springConfig);
      translateY.value = withSpring(y, springConfig);
    });
    return () => subscription.remove();
  }, [bounds, checkBounds, maxX, maxY, translateX, translateY]);

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [
      { translateX: translateX.value },
      { translateY: translateY.value },
    ],
  }));

  return (
    <GestureDetector gesture={gesture}>
      <Animated.View
        ref={ref}
        style={
          [
            styles.base,
            disabled && styles.disabled,
            animatedStyle,
            style,
          ] as unknown as ViewStyle
        }
        onLayout={(event) => {
          const { width, height } = event.nativeEvent.layout;
          elementSize.value = { width, height };
          const { x, y } = checkBounds(translateX.value, translateY.value);
          translateX.value = withSpring(x, springConfig);
          translateY.value = withSpring(y, springConfig);
        }}
        {...props}
      />
    </GestureDetector>
  );
});

Draggable.displayName = "Draggable";
export { Draggable };
